#include "CAIModule_GoTo.h"
#include "CAIModule_ChasePlayer.h"
#include "CAnnouncementsPanel.h"
#include "CGame.h"
#include "CPlayer.h"

#include "CEntity.h"
#include <iostream>
#include <cstring>

std::map<std::string, SRace> CEntity::Races;

extern int koboldsKilled;

CEntity::CEntity(std::string name) : Dead(false), CurrentInventory('-'), CurrentFloor(0), Health(0), Level(0), MaxHealth(0), TicksSinceCombat(0), CanSeePlayer(false), Module(NULL)
{
	Name = name;
}

CEntity::~CEntity()
{
	if (Module)
		delete Module;
}

bool CEntity::canSeePlayer()
{
	return CanSeePlayer;
}

bool CEntity::isDead()
{
	return Dead;
}

float CEntity::getSkill(std::string key)
{
	if (Skills.find(key) == Skills.end())
		Skills[key] = 0.0f;

	return Skills[key];
}

int CEntity::getAttribute(std::string key)
{
	if (Attributes.find(key) == Attributes.end())
		Attributes[key] = 0;

	return Attributes[key];
}

int CEntity::getCurrentFloor()
{
	return CurrentFloor;
}

int CEntity::getHealth()
{
	return Health;
}

int CEntity::getLevel()
{
	return Level;
}

int CEntity::getMaxHealth()
{
	return MaxHealth;
}

int CEntity::getSight()
{
	return Races[Race].Sight;
}

int CEntity::getSpeed()
{
	return Races[Race].Speed;
}

std::string CEntity::getRace()
{
	return Race;
}

int CEntity::attack(CEntity* target)
{
	int damRoll = 0;
	int max;

	if (EquipSlots[EES_WIELD].Occupied)
	{
		SBaseItem item = CItem::Items[EquipSlots[EES_WIELD].Item->getBaseName()];
		SMaterial mat = CItem::Materials[EquipSlots[EES_WIELD].Item->getMaterial()];

		for (int i = 0; i < item.Damage.nb_dices; ++i)
			damRoll += TCODRandom::getInstance()->getInt(1, item.Damage.nb_faces);
		damRoll += item.Damage.addsub;
		damRoll += Attributes["Strength"]/4;
		damRoll *= mat.DamageModifier;
		max = ((item.Damage.nb_dices*item.Damage.nb_faces)+(Attributes["Strength"]/4))*mat.DamageModifier;
	}
	else
	{
	    for (int i = 0; i <  Races[Race].UnarmedDie.nb_dices; ++i)
			damRoll += TCODRandom::getInstance()->getInt(1,  Races[Race].UnarmedDie.nb_faces);
		damRoll += Attributes["Strength"]/4;
		max = ((Races[Race].UnarmedDie.nb_dices*Races[Race].UnarmedDie.nb_faces)+(Attributes["Strength"]/4));
	}

	std::stringstream message;
	message << Name << " attacking "<< target->getName() << ". Hit for "<< damRoll << "/" << max;
	CAnnouncementsPanel::addAnnouncement(message.str());

	target->damage(damRoll);
	return getSpeed();
}

void CEntity::damage(int damage)
{
	TicksSinceCombat = CTurnSchedule::getTicks();

	Health -= damage;
	std::cout<<Name<<" has "<<Health<<"hp left."<<std::endl;
	if (Health <= 0)
	{
		CAnnouncementsPanel::addAnnouncement((Name+" is dead.").c_str());
		std::cout<<Name<<" is dead."<<std::endl;
		koboldsKilled++;
		Dead = true;
	}
}

void CEntity::setAttribute(std::string key, int value)
{
	Attributes[key] = value;
}

void CEntity::init(std::string race)
{
	Race = race;
	MaxHealth = 0;
	for (int i = 0; i < Races[Race].HitDie.nb_dices; ++i)
        MaxHealth += TCODRandom::getInstance()->getInt(1, Races[Race].HitDie.nb_faces);
	MaxHealth += Attributes["Endurance"]/4;

	Health = MaxHealth;
	Shape = Races[Race].Shape;

	bool placed = false;
	while (!placed)
	{
		int x = TCODRandom::getInstance()->getInt(0, gGame->getDungeon()->getFloor(CurrentFloor)->getSize().x()-1);
		int y = TCODRandom::getInstance()->getInt(0, gGame->getDungeon()->getFloor(CurrentFloor)->getSize().y()-1);
		if (gGame->getDungeon()->getFloor(0)->getTile(pair(x, y))->isPassable())
		{
			Position = pair(x, y);
			LastPosition = Position;
			placed = true;
		}
	}

	Module = new CAIModule_ChasePlayer(this);
}

void CEntity::setLevel(int level)
{
	Level = level;
}

void CEntity::setRace(std::string race)
{
	Race = race;
	MaxHealth = 0;
	for (int i = 0; i < Races[Race].HitDie.nb_dices; ++i)
        MaxHealth += TCODRandom::getInstance()->getInt(1, Races[Race].HitDie.nb_faces);
	MaxHealth += Attributes["Endurance"]/4;
	Health = MaxHealth;
}

void CEntity::setSkill(std::string key, float value)
{
	Skills[key] = value;
}

void CEntity::takeTurn()
{
	if (Dead)
	{
		//do dead things
	}
	else
	{
		int delay = getSpeed();

		if ((int)CTurnSchedule::getTicks()-TicksSinceCombat >= getSpeed()*4 && Health < MaxHealth)
		{
			TicksSinceCombat = CTurnSchedule::getTicks();
			++Health;
		}

		TCODMap* fov = gGame->getDungeon()->getFloor(CurrentFloor)->getFovMap();
		fov->computeFov(Position.x(), Position.y(), getSight(), true);
		if (fov->isInFov(gPlayer->getPosition().x(), gPlayer->getPosition().y()))
			CanSeePlayer = true;
		else
			CanSeePlayer = false;

		if (Module)
			delay = Module->action();

		CTurnSchedule::scheduleTurn(delay, this);
	}
}

class CRaceDataListener : public ITCODParserListener
{
	std::string Current;

	bool parserNewStruct(TCODParser* parser, const TCODParserStruct* str, const char* name)
	{
		if (!name)
			return false;

		Current = name;

		return true;
	}

	bool parserFlag(TCODParser* parser,const char* name)
	{
		return true;
	}

	bool parserProperty(TCODParser* parser, const char* name, TCOD_value_type_t type, TCOD_value_t value)
	{
		if (_strcmpi(name, "key") == 0)
			CEntity::Races[Current].Key = value.c;
		else if (_strcmpi(name, "shape") == 0)
			CEntity::Races[Current].Shape = value.c;
		else if (_strcmpi(name, "size") == 0)
			CEntity::Races[Current].Size = (ERaceSize)value.i;
		else if (_strcmpi(name, "speed") == 0)
			CEntity::Races[Current].Speed = value.i;
		else if (_strcmpi(name, "sight") == 0)
			CEntity::Races[Current].Sight = value.i;
        else if (_strcmpi(name, "hitDice") == 0)
			CEntity::Races[Current].HitDie = value.dice;
        else if (_strcmpi(name, "unarmedDice") == 0)
			CEntity::Races[Current].UnarmedDie = value.dice;

		return true;
	}

	bool parserEndStruct(TCODParser* parser, const TCODParserStruct* str, const char* name)
	{
		return true;
	}

	void error(const char* msg)
	{
		fprintf(stderr, "%s", msg);
		exit(1);
	}

public:
	CRaceDataListener() : ITCODParserListener() {}

};

void CEntity::loadRaces()
{
	TCODParser parser = TCODParser();
	TCODParserStruct* itemStruct = parser.newStructure("race");
	itemStruct->addProperty("key", TCOD_TYPE_CHAR, true);
	itemStruct->addProperty("shape", TCOD_TYPE_CHAR, true);
	itemStruct->addProperty("size", TCOD_TYPE_INT, true);
	itemStruct->addProperty("speed", TCOD_TYPE_INT, true);
	itemStruct->addProperty("sight", TCOD_TYPE_INT, true);
	itemStruct->addProperty("hitDice", TCOD_TYPE_DICE, true);
	itemStruct->addProperty("unarmedDice", TCOD_TYPE_DICE, true);

	CRaceDataListener* listener = new CRaceDataListener();
	parser.run("data/races", listener);
	delete listener;
}
